import * as THREE0 from 'three'
import {Lensflare, LensflareElement} from 'three/examples/jsm/objects/Lensflare'
import {FlyControls} from 'three/examples/jsm/controls/FlyControls'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
import {TWEEN} from 'three/examples/jsm/libs/tween.module.min'
import {TrackballControls} from 'three/examples/jsm/controls/TrackballControls'
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
import {STLLoader} from 'three/examples/jsm/loaders/STLLoader'
import {OBJLoader} from 'three/examples/jsm/loaders/OBJLoader'
import {MTLLoader} from 'three/examples/jsm/loaders/MTLLoader'
import {GCodeLoader} from 'three/examples/jsm/loaders/GCodeLoader'
import {GridHelper} from 'three'
import {EffectComposer} from 'three/examples/jsm/postprocessing/EffectComposer'
import {RenderPass} from 'three/examples/jsm/postprocessing/RenderPass'
import {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass'
import {DotScreenShader} from 'three/examples/jsm/shaders/DotScreenShader'
import {RGBShiftShader} from 'three/examples/jsm/shaders/RGBShiftShader'
import {CSS3DRenderer, CSS3DObject} from 'three/examples/jsm/renderers/CSS3DRenderer'
import {CSS2DRenderer, CSS2DObject} from 'three/examples/jsm/renderers/CSS2DRenderer'
import SweepingLightShader from './SweepingLightShader'
import {OutlinePass} from 'three/examples/jsm/postprocessing/OutlinePass'
import {UnrealBloomPass} from 'three/examples/jsm/postprocessing/UnrealBloomPass'
import * as utils from './utils'
import locationIcon from './textures/location.png'
import {LineGeometry} from 'three/examples/jsm/lines/LineGeometry'
import {LineMaterial} from 'three/examples/jsm/lines/LineMaterial'
import {Line2} from 'three/examples/jsm/lines/Line2'
import './style.scss'
import {createApp} from 'vue'

const LOG_TAG = 'threejs-tools'

let THREE = THREE0

// 初始化一个默认的场景、相机、渲染器，光照等设置
export function initDefault(el, env, {
  customTHREE = undefined,
  cameraPosition = [0, 0, 4000],
  cameraLookAt = [0, 0, 0],
  light = true,
  clearColor,
  axesSize = 300,
} = {}) {
  THREE = customTHREE || THREE
  const width = el.clientWidth
  const height = el.clientHeight

  // 场景，相机，渲染器
  env.width = width
  env.height = height
  env.scene = new THREE.Scene()
  env.renderer = new THREE.WebGLRenderer({antialias: true, alpha: true})
  // env.renderer.setClearColor(0x100831)

  clearColor && env.renderer.setClearColor(clearColor)
  env.renderer.shadowMap.enabled = true
  env.renderer.setSize(width, height)
  env.renderer.domElement.style.position = 'absolute'
  env.renderer.domElement.style.top = 0

  axesSize && addAxes(env, axesSize)

  env.css3DRenderer = new CSS3DRenderer()
  env.css3DRenderer.setSize(width, height)
  env.css3DRenderer.domElement.style.position = 'absolute'
  env.css3DRenderer.domElement.style.top = 0
  env.css3DRenderer.domElement.style['transition'] = 'all 2s linear'
  env.css3DRenderer.domElement.style['pointer-events'] = 'none'
  env.css3DRenderer.domElement.className = LOG_TAG + '-css3d-renderer'
  env.css3DRenderer.domElement.addEventListener('click', ev => ev.target.onClick(ev))
  // env.css3DRenderer.domElement.addEventListener('dblclick', ev => ev.target.onClick(ev))

  env.css2DRenderer = new CSS2DRenderer()
  env.css2DRenderer.setSize(width, height)
  env.css2DRenderer.domElement.style.position = 'absolute'
  env.css2DRenderer.domElement.style.top = 0
  env.css2DRenderer.domElement.style['transition'] = 'all 2s linear'
  env.css2DRenderer.domElement.style['pointer-events'] = 'none'
  env.css2DRenderer.domElement.className = LOG_TAG + '-css2d-renderer'

  el.appendChild(env.renderer.domElement)
  el.appendChild(env.css3DRenderer.domElement)
  el.appendChild(env.css2DRenderer.domElement)

  env.camera = new THREE.PerspectiveCamera(75, width / height, 1, 500000)
  // env.camera = new THREE.OrthographicCamera(-80000, 80000, 70000, -70000, 0.1, 250000)
  env.camera.position.set(...cameraPosition)
  // env.camera.lookAt(...cameraLookAt)
  initOrbitControl(env, cameraLookAt)

  light && addDefaultLight(env)
  // fitCamera(env)

  window.addEventListener('resize', () => {
    env.camera.aspect = el.clientWidth / el.clientHeight
    env.camera.updateProjectionMatrix()
    env.renderer.setSize(el.clientWidth, el.clientHeight)
    env.css3DRenderer.setSize(el.clientWidth, el.clientHeight)
    env.css2DRenderer.setSize(el.clientWidth, el.clientHeight)
  })

  function animate(env) {
    const {composer, css3DRenderer, css2DRenderer, renderer, scene, camera, onAnimationFrame} = env
    onAnimationFrame && onAnimationFrame()
    css3DRenderer && css3DRenderer.render(scene, camera)
    css2DRenderer && css2DRenderer.render(scene, camera)
    composer ? composer.render() : renderer.render(scene, camera)
    TWEEN.update()
    requestAnimationFrame(() => animate(env))
  }

  animate(env)
}

export function addPass(env, pass) {
  if (!env.composer) {
    env.composer = new EffectComposer(env.renderer)
    env.composer.setSize(env.width, env.height)
    env.renderPass = new RenderPass(env.scene, env.camera)
    env.composer.addPass(env.renderPass)
  }
  env.composer.addPass(pass)
}

export function createUnrealBloomPass({strength = .5, radius = 1.5, threshold = 0.15} = {}) {
  const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), strength, radius, threshold)
  bloomPass.renderToScreen = true
  // bloomPass.threshold = 0
  // bloomPass.strength = .8
  // bloomPass.radius = .5
  return bloomPass
}

export function createDialog(point, parent, option) {
  const dialog = new THREE.Object3D()
  dialog.name = 'dialog'

  const [x, y, z] = point.coord
  const linePoints = [
    point.coord,
    [x, y + 100, z],
    // [x * 2, y + 100, z * 2],
    // [x - 30, y + 130, z + 30],
  ]
  dialog.line = addLine2(dialog, linePoints.flat(), '#f00', 1)
  dialog.line.options = {points: linePoints}
  dialog.board = createCssObject(point.html, {
    ...option,
    position: linePoints[linePoints.length - 1],
    listeners: point.listeners,
    props: point.props,
  }, dialog)
  dialog.board.el.dialog = dialog

  parent && parent.add(dialog)
  return dialog
}

export function addLabel(parent, text = 'LABEL', position = [0, 0, 0], {
  style = {color: '#fff', fontSize: '1.2rem'}
} = {}) {
  const labelDiv = document.createElement('div')
  labelDiv.className = 'label'
  labelDiv.textContent = text
  Object.assign(labelDiv.style, style)
  // labelDiv.style.marginTop = '-1em'
  const labelObj = new CSS2DObject(labelDiv)
  labelObj.position.set(...position)
  parent.add(labelObj)
}

function createCssObject(html, {
  width, height, onClick, position, rotation,
  css3DRenderer, css2DRenderer,
  camera, controls,
  props = {}, listeners = {},
}, parent) {
  html = html || `
  <div style="width: ${width}px; height: ${height}px; background: url(/images/collectDataBgc.svg) no-repeat; background-size: 100%; padding: 20px;">
    <span>hello world!</span>
  </div>
  `

  var div = document.createElement('div')
  // div.style.width = w + 'px';
  // div.style.height = h + 'px';
  div.className = 'threejs-tools-css3d-object'
  let el
  let cssObject

  const onEvent = ev => {
    const cssObj = ev ? ev.target.cssObj : cssObject
    cssObj.focus = !cssObj.focus
    onClick && onClick(cssObj.el.dialog)
    if (cssObj.focus) {
      css3DRenderer.domElement.style.background = '#000000ee'
      css2DRenderer.domElement.style.opacity = .1
      const [x, y, z] = ['x', 'y', 'z'].map(k => cssObj.position[k])
      moveCamera({camera: camera, controls: controls}, {newP: [x, y + 80, z + 50], newT: cssObj.position})
    } else {
      css3DRenderer.domElement.style.background = 'none'
      css2DRenderer.domElement.style.opacity = 1
      moveCamera({camera: camera, controls: controls})
    }
  }

  if (html.render) {
    // console.log(LOG_TAG, 'createCssObject--listeners', listeners)
    listeners.onEvent = onEvent
    const vue = createApp(html, {listeners, ...props}).mount(div)
    // const vue = new Vue({
    //   el: div,
    //   components: {Root: html},
    //   props: {
    //     listeners: {type: Object, default: () => listeners || {}}
    //   },
    //   template: `<Root v-on="listeners"/>`,
    // })
    cssObject = new CSS3DObject(vue.$el)
    el = vue.$el.children[0]
  } else {
    div.innerHTML = html
    cssObject = new CSS3DObject(div)
    el = div.children[0]
  }

  cssObject.position.set(...position)
  cssObject.rotation.set(...rotation)
  cssObject.el = el
  cssObject.el.cssObj = cssObject
  cssObject.el.onClick = onEvent

  cssObject.options = {
    width, height, onClick, position, rotation,
    css3DRenderer, css2DRenderer,
    html, listeners, camera, controls
  }
  parent && parent.add(cssObject)
  return cssObject
}

export function addLine2(parent, positions, color, linewidth) {
  const line = createLine2(positions, color, linewidth)
  parent.add(line)
  return line
}

export function createLine2(positions, color = '#54E5B5', linewidth = 2) {
  const geometry = new LineGeometry()
  geometry.setPositions(positions)
  const material = new LineMaterial({color, linewidth})
  // 把渲染窗口尺寸分辨率传值给材质LineMaterial的resolution属性
  // resolution属性值会在着色器代码中参与计算
  material.resolution.set(window.innerWidth, window.innerHeight)
  const line = new Line2(geometry, material)
  return line
}

function createPlane(w, h, position, rotation) {
  var material = new THREE.MeshBasicMaterial({
    color: 0x000000,
    opacity: 0.0,
    side: THREE.DoubleSide
  })

  var geometry = new THREE.PlaneGeometry(w, h)

  var mesh = new THREE.Mesh(geometry, material)

  mesh.position.x = position.x
  mesh.position.y = position.y
  mesh.position.z = position.z

  mesh.rotation.x = rotation.x
  mesh.rotation.y = rotation.y
  mesh.rotation.z = rotation.z

  return mesh
}

export async function loadFont(font = '/fonts/SimHei_Regular.json') {
  const loader = new THREE.FontLoader()
  return new Promise((resolve, reject) => {
    loader.load(font, function (response) {
      resolve(response)
    })
  })
}

export async function addText({scene}, pos, text, {size = 5, height = 1, font} = {}) {
  font = font || await loadFont()
  const textGeo = new THREE.TextGeometry(text, {size, height, font})
  const materials = [
    new THREE.MeshPhongMaterial({color: '#aaaa00', flatShading: true}), // front
    new THREE.MeshPhongMaterial({color: '#888800'}) // side
  ]
  const textMesh1 = new THREE.Mesh(textGeo, materials)

  textMesh1.position.set(...pos)
  textGeo.computeBoundingBox()
  const centerOffset = 0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x)
  textMesh1.position.x -= centerOffset
  textMesh1.rotation.x = -Math.PI * .5
  scene.add(textMesh1)

  return textMesh1

  group.add(textMesh1)
  if (mirror) {
    textMesh2 = new THREE.Mesh(textGeo, materials)
    textMesh2.position.x = centerOffset
    textMesh2.position.y = -hover
    textMesh2.position.z = height
    textMesh2.rotation.x = Math.PI
    textMesh2.rotation.y = Math.PI * 2
    group.add(textMesh2)
  }
}

//r 圆半径
//init 初始圆半径
//ring 圆环大小
//color 颜色 THREE.Vector3
//speed 速度
export function createRipple({r = 20, init = 10, ring = 10, color = '#4a4', speed = .1}, parent) {
  const uniform = {
    u_color: {value: new THREE.Color(color)},
    u_r0: {value: r},
    u_r: {value: r},
    u_r2: {value: r * .5},
    u_ring: {
      value: ring,
    },
  }

  const vs = `
		varying vec3 vPosition;
		void main() {
			vPosition = position;
			gl_Position	= projectionMatrix * modelViewMatrix * vec4(position, 1.0);
		}
	`
  const fs = `
		varying vec3 vPosition;
		uniform vec3 u_color;
		uniform float u_r0;
		uniform float u_r;
		uniform float u_r2;
		uniform float u_ring;

		void main() {
			float pct = distance(vec2(vPosition.x, vPosition.y), vec2(0.0));
      float dis = 0.0;
      float dis2 = 0.0;

      float inner = u_r - u_ring;
      if (inner < 0.0) {
        inner = 0.0;
      }

			if(pct < u_r && pct > inner) {
				dis = (pct - inner) / inner;
			}
      if (u_r > 0.8 * u_r0) {
        dis = dis * (u_r0 - u_r) / (0.2 * u_r0);
      }

      inner = u_r2 - u_ring;
      if (inner < 0.0) {
        inner = 0.0;
      }
			if(pct < u_r2 && pct > inner) {
				dis2 = (pct - inner) / inner;
			}
      if (u_r2 > 0.8 * u_r0) {
        dis2 = dis2 * (u_r0 - u_r2) / (0.2 * u_r0);
      }

      dis = dis + dis2;

      gl_FragColor = vec4(u_color, dis);
		}
	`
  const geometry = new THREE.CircleGeometry(r, 120)
  const material = new THREE.ShaderMaterial({
    vertexShader: vs,
    fragmentShader: fs,
    side: THREE.DoubleSide,
    uniforms: uniform,
    transparent: true,
    // depthWrite: true,
    depthTest: true,
  })
  const circle = new THREE.Mesh(geometry, material)
  circle.rotateX(Math.PI / 2)
  circle.renderOrder = 1

  function render() {
    uniform.u_r.value += speed || 0.1
    uniform.u_r2.value += speed || 0.1
    if (uniform.u_r.value >= r) {
      uniform.u_r.value = 0
    }
    if (uniform.u_r2.value >= r) {
      uniform.u_r2.value = 0
    }
    requestAnimationFrame(render)
  }

  render()

  parent && parent.add(circle)
  return circle
}

export function addInstanceMesh({scene, camera, controls}, points, name, {
  geometry, material, scale = 1, size = 32
} = {}) {
  // check size type
  if (typeof (size) === 'number') {
    size = [size, size, size]
  }
  // create geometry
  geometry = geometry || new THREE.BoxGeometry(...size)
  const center = getCenter(geometry)
  geometry.translate(-center.x, 0, -center.z)
  geometry.scale(scale, scale, scale)
  // geometry._scale = scale

  // create material
  material = material || new THREE.MeshPhongMaterial({color: 0xff0000})
  material.transparent = true
  material.opacity = .5

  // create instanced mesh
  const mesh = new THREE.InstancedMesh(geometry, material, points.length)
  mesh.name = name
  mesh.opacity = .5
  mesh._color = new THREE.Color('#c1a476')
  scene.add(mesh)

  const dummy = new THREE.Object3D() // use for calc matrix

  function _render() {
    // const cameraDist = camera.position.distanceTo(new THREE.Vector3(0, 0, 0))
    // const scale = Math.log10(cameraDist / 100 + 1) * .3
    // console.log(scale)
    points.forEach((p, i) => {
      // set dummy for point i and calc matrix
      dummy.position.set(p[0], p[1], p[2])
      dummy.scale.set(scale, scale, scale)
      dummy.updateMatrix()
      // update mesh matrix i
      mesh.setMatrixAt(i, dummy.matrix)
      mesh.setColorAt(i, mesh._color)
      mesh.instanceMatrix.needsUpdate = true
    })
  }

  _render()
  controls.addEventListener('change', _render)

  return mesh
}

export function createPoints(points, parent, {camera, controls}, {size = 20, texture = locationIcon}) {
  const vertices = points.flat()
  const geometry = new THREE.BufferGeometry()
  geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))
  const material = new THREE.PointsMaterial({size, map: loadTexture(texture), transparent: true, depthTest: false})
  const pointsMesh = new THREE.Points(geometry, material)
  pointsMesh.position.y = material.size / 2
  pointsMesh.transparent = true

  function _render() {
    const cameraDist = camera.position.distanceTo(new THREE.Vector3(0, 0, 0))
    const scale = cameraDist / 300
    pointsMesh.material.size = size * scale
    pointsMesh.needsUpdate = true
  }
  controls.addEventListener('change', _render)
  _render()

  parent && parent.add(pointsMesh)
  return pointsMesh
}

export function addSprites({scene}, points = [], texture = locationIcon) {
  texture = loadTexture(texture)
  texture.needsUpdate = true //注意这句不能少
  const material = new THREE.SpriteMaterial({map: texture, depthTest: false})
  material.transparent = true

  const sprites = points.forEach(p => {
    const sprite = new THREE.Sprite(material)
    sprite.scale.set(20, 20, 1)
    sprite.position.set(p[0], 10, p[1])
    sprite.renderOrder = 10000
    scene.add(sprite)
    return sprite
  })
  return sprites
}

export function addSkyBox({scene}, dirPathOrUrls = '/textures/sky-box/', names = ['back', 'front', 'up', 'down', 'left', 'right'], format = '.jpg') {
  let urls
  if (dirPathOrUrls instanceof Array) {
    urls = dirPathOrUrls
  } else {
    const path = dirPathOrUrls
    urls = names.map(name => path + name + format)
  }

  const materials = []
  for (let i = 0; i < urls.length; ++i) {
    const loader = new THREE.TextureLoader()
    // loader.setCrossOrigin(this.crossOrigin);
    const texture = loader.load(urls[i], function () {
    }, undefined, function () {
    })
    materials.push(new THREE.MeshBasicMaterial({
      map: texture,
      side: THREE.BackSide,
      // transparent: true,
      // needsUpdate:true
    }))
  }
  const cube = new THREE.Mesh(new THREE.BoxGeometry(9000, 9000, 9000), materials)
  cube.name = 'sky'
  scene.add(cube)

  // const axes = new THREE.AxesHelper(100000)
  // scene.add(axes)
}

export function loadTexture(texture) {
  const textureLoader = new THREE.TextureLoader()
  return textureLoader.load(texture)
}

export function addTube({scene}, curve, texture, radius = 1) {
  const tubeGeometry = new THREE.TubeGeometry(curve, 200, radius, 100, false)

  // 设置阵列模式 RepeatWrapping
  texture.wrapS = THREE.RepeatWrapping
  texture.wrapT = THREE.RepeatWrapping
  // 设置x方向的重复数(沿着管道路径方向)
  // 设置y方向的重复数(环绕管道方向)
  texture.repeat.x = 64
  texture.repeat.y = 4
  // 设置管道纹理偏移数,便于对中
  texture.offset.y = 0.5

  const tubeMaterial = new THREE.MeshPhongMaterial({map: texture, transparent: true})

  const tube = new THREE.Mesh(tubeGeometry, tubeMaterial)
  scene.add(tube)
}

export function addCurve({scene}, {p0 = [0, 0, 0], p1, p2 = [300, 0, 0]} = {}) {
  let dist = [0, 1, 2].map(i => p0[i] - p2[i]).map(di => di * di).reduce((sum, num) => sum + num, 0)
  dist = Math.sqrt(dist)

  p1 = p1 || [(p0[0] + p2[0]) * .5, 100, (p0[2] + p2[2]) * .5]
  p1[1] = dist * .4

  const points = [p0, p1, p2].map(p => new THREE.Vector3(p[0], p[1], p[2]))
  const curve = new THREE.QuadraticBezierCurve3(...points)

  const positions = []
  curve.getPoints(50)
    .forEach(vec => {
      positions.push(vec.x, vec.y, vec.z)
    })
  // console.log(LOG_TAG, 'addCurve--positions:', positions)

  const line = createLine2(positions)
  scene.add(line)

  line.progress = 0

  // setInterval(() => {
  //   line.progress && line.geometry.setDrawRange(50 - line.progress, line.progress)
  //   line.geometry.attributes.position.needsUpdate = true
  //   line.progress = (line.progress + 1) % 50
  // }, 20)

  return {curve, line}
}

export function addAxes(ctx, size) {
  // const axesHelper = new THREE.AxesHelper(size)
  // ctx.scene.add(axesHelper)

  const axes = new THREE.Object3D()
  ctx.scene.add(axes)

  const headLen = size * .1

  const xAxes = new THREE.ArrowHelper(new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 0), size, 0xff0000, headLen)
  axes.add(xAxes)
  const yAxes = new THREE.ArrowHelper(new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 0), size, 0x00ff00, headLen)
  axes.add(yAxes)
  const zAxes = new THREE.ArrowHelper(new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, 0), size, 0x0000ff, headLen)
  axes.add(zAxes)
}

export function addGrid(env, {
  center = [0, 0, 0], size = 800, divisions = 20,
} = {}) {
  if (center.x && center.y && center.z) {
    center = [center.x, center.y, center.z]
  }
  const gridHelper = new GridHelper(size, divisions, new THREE.Color(1, 0, 0), new THREE.Color(0, 1, 0))
  gridHelper.name = 'grid'
  gridHelper.position.set(...center)
  env.scene.add(gridHelper)
  return gridHelper
}

export function addPlane({scene}, [width, height, depth] = [300, 300, 10], {color = '#00ff00', position = [0, 0, 0]} = {}) {
  const geometry = new THREE.BoxGeometry(width, height, depth)
  geometry.name = 'plane-geometry'

  const material = new THREE.MeshLambertMaterial({color})
  material.name = 'plane-material'

  const plane = new THREE.Mesh(geometry, material)
  plane.receiveShadow = true
  plane.rotation.x = -0.5 * Math.PI
  plane.position.set(...position)
  plane.name = 'plane'
  scene.add(plane)
  return plane
}

export function initOrbitControl(env, cameraLookAt) {
  const controls = new OrbitControls(env.camera, env.renderer.domElement)
  controls.enableDamping = true
  controls.dampingFactor = 0.25
  controls.rotateSpeed = 1.35

  controls.minZoom = 0.5
  controls.maxZoom = 50
  controls.minDistance = 20
  // controls.maxDistance = 800

  // controls.minPolarAngle = 0
  // controls.maxPolarAngle = Math.PI * .6
  // controls.minAzimuthAngle = -Math.PI * (50 / 180)
  // controls.maxAzimuthAngle = Math.PI * (50 / 180)

  controls.autoRotate = true
  controls.autoRotateSpeed = 2.0
  // controls.target = new THREE.Vector3(0, 10000, 0);
  // controls.target.set(...cameraLookAt)
  controls.enableKeys = true

  controls.addEventListener('change', ev => {
    // console.log(ev)
  })

  env.controls = controls
  return controls
}

export function moveCamera({camera, controls}, {newP = camera.lastPosition || camera.position, newT = controls.lastTarget || controls.target, oldP = camera.position, oldT = controls.target, duration = 1000, easing = TWEEN.Easing.Linear.None, callback} = {}) {
  camera.lastPosition = new THREE.Vector3(...['x', 'y', 'z'].map(k => camera.position[k]))
  controls.lastTarget = new THREE.Vector3(...['x', 'y', 'z'].map(k => controls.target[k]))
  if (newP instanceof Array) {
    newP = new THREE.Vector3(...newP)
  }
  if (newT instanceof Array) {
    newT = new THREE.Vector3(...newT)
  }
  const from = {x1: oldP.x, y1: oldP.y, z1: oldP.z, x2: oldT.x, y2: oldT.y, z2: oldT.z}
  const to = {x1: newP.x, y1: newP.y, z1: newP.z, x2: newT.x, y2: newT.y, z2: newT.z}
  const tween = new TWEEN.Tween(from)
  tween.to(to, duration)

  controls.enabled = false
  // 每一帧执行函数 、这个地方就是核心了、每变一帧跟新一次页面元素
  tween.onUpdate((object) => {
    camera.position.set(object.x1, object.y1, object.z1)
    controls.target = new THREE.Vector3(object.x2, object.y2, object.z2)
    // controls.target.x = object.x2;
    // controls.target.y = object.y2;
    // controls.target.z = object.z2;
    controls.update()
  })

  tween.easing(easing)
  // 这个函数必须有、这个是启动函数、不加不能启动
  tween.start()

  return new Promise((resolve, reject) => {
    // 动画完成后的执行函数
    tween.onComplete(() => {
      controls.enabled = true
      // this.tweenCallBack && this.tweenCallBack();
      // console.log('moveCamera--onComplete', camera.position, controls.target)
      resolve()
    })
  })
}

export function initTrackballControls(env) {
  const trackballControls = new TrackballControls(env.camera, env.renderer.domElement)

  trackballControls.rotateSpeed = 1.0
  trackballControls.zoomSpeed = 1.0
  trackballControls.panSpeed = 1.0
  trackballControls.noZoom = false
  trackballControls.noPan = false
  trackballControls.staticMoving = true
  return trackballControls
}

export function initFlyControls(env) {
  const flyControls = new FlyControls(env.camera, env.renderer.domElement)

  flyControls.movementSpeed = 25
  flyControls.rollSpeed = Math.PI / 24
  flyControls.autoForward = false
  flyControls.dragToLook = false
  return flyControls
}

export function fitCener(mesh, x, y, z) {
  const size = new THREE.Box3().setFromObject(mesh).size()
  mesh.size = size
  mesh.position.set(-size.x / 2, 0, size.z / 2)
}

export function getBoundingSphere(mesh) {
  mesh.geometry.computeBoundingSphere()
  return mesh.geometry.boundingSphere
}

export function fitView(env, mesh) {
  const boundingSphere = getBoundingSphere(mesh)
  env.camera.lookAt(boundingSphere.center)
  env.controls.target.set(...boundingSphere.center)
  env.camera.zoom = env.height / boundingSphere.radius / 2
  env.camera.updateProjectionMatrix()
  return boundingSphere
}

export function fitCamera(env, mesh) {
  // fit camera to object
  const bBox = new THREE.Box3().setFromObject(mesh)
  const height = bBox.size().y
  const dist = height / (2 * Math.tan(env.camera.fov * Math.PI / 360))
  const pos = env.scene.position

  // fudge factor so the object doesn't take up the whole view
  env.camera.position.set(pos.x, pos.y, dist * 1.5)
  env.camera.lookAt(pos)
}

export function addDefaultLight(env) {
  let light
  light = new THREE.AmbientLight('#aff', 0.6)
  // light.position.set(-50, 120, 80)
  light.name = 'light-环境光'
  env.scene.add(light)

  light = new THREE.DirectionalLight('#aff', 1)
  light.position.set(300, 1000, 3000)
  light.name = 'light-平行光'
  env.scene.add(light)

  light = new THREE.DirectionalLight('#aff', .6)
  light.position.set(300, 1000, -3000)
  light.name = 'light-平行光'
  env.scene.add(light)
}

export function addLensFlare(env) {
  // lensflares
  const textureLoader = new THREE.TextureLoader()

  const textureFlare0 = textureLoader.load('/textures/lensflare/lensflare0.png')
  const textureFlare3 = textureLoader.load('/textures/lensflare/lensflare3.png')

  // addLight(0.55, 0.9, 0.5, 5000, 0, -2000)
  // addLight(0.08, 0.8, 0.5, 0, 0, -2000)
  _addLight(env, '#5585d0', 0, 20, -2000)

  function _addLight({scene}, color, x, y, z) {
    const light = new THREE.PointLight(color, 1.5, 200000)
    light.position.set(x, y, z)
    scene.add(light)

    const lensflare = new Lensflare()
    lensflare.addElement(new LensflareElement(textureFlare0, 700, 0, light.color))
    // lensflare.addElement(new LensflareElement(textureFlare3, 60, 0.6))
    // lensflare.addElement(new LensflareElement(textureFlare3, 70, 0.7))
    // lensflare.addElement(new LensflareElement(textureFlare3, 120, 0.9))
    // lensflare.addElement(new LensflareElement(textureFlare3, 70, 1))
    light.add(lensflare)
  }
}

export function getSize(geometry) {
  geometry.computeBoundingBox()
  const maxX = geometry.boundingBox.max.x
  const minX = geometry.boundingBox.min.x
  const maxY = geometry.boundingBox.max.y
  const minY = geometry.boundingBox.min.y
  const maxZ = geometry.boundingBox.max.z
  const minZ = geometry.boundingBox.min.z
  return {x: maxX - minX, y: maxY - minY, z: maxZ - minZ}
}

export function getCenter(geometry) {
  geometry.computeBoundingBox()
  console.log('boundingBox', geometry.boundingBox)
  const maxX = geometry.boundingBox.max.x
  const minX = geometry.boundingBox.min.x
  const maxY = geometry.boundingBox.max.y
  const minY = geometry.boundingBox.min.y
  const maxZ = geometry.boundingBox.max.z
  const minZ = geometry.boundingBox.min.z
  return {x: (maxX + minX) / 2, y: (maxY + minY) / 2, z: (maxZ + minZ) / 2}
}

export function gcodeLoad(model) {
  const loader = new GCodeLoader()
  loader.load('models/gcode/benchy.gcode', function (object) {
    object.position.set(-100, -20, 100)
    this.scene.add(object)
  })
}

export function loadMtl(mtl) {
  const mtlLoader = new MTLLoader()
  // mtlLoader.setPath('/model/')
  return new Promise((resolve, reject) => {
    mtlLoader.load(mtl, materials => {
      materials.preload()
      resolve(materials)
    })
  })
}

export function loadGltf(model, materials) {
  const loader = new GLTFLoader()
  // loader.setPath('/model/')
  materials && loader.setMaterials(materials)
  return new Promise((resolve, reject) => {
    loader.load(model,
      gltf => resolve(gltf.scene),
      progress => console.log(progress),
      err => {
        reject(err)
      },
    )
  })
    .catch(err => {
      console.error(err)
      throw err
    })
}

export function loadStl(model) {
  var loader = new STLLoader()
  return new Promise((resolve, reject) => {
    loader.load(model, (event) => {
      var geometry = event

      var material = new THREE.MeshPhongMaterial({color: 0x33ddff, specular: 0x111111, shininess: 200})
      // var material = new THREE.MeshBasicMaterial({color: '#fff', side: THREE.DoubleSide})
      var mesh = new THREE.Mesh(geometry, material)

      // mesh.position.set(0, - 0.25, 0.6)
      // mesh.rotation.set(0, Math.PI / 2, 0)
      // mesh.scale.set(0.5, 0.5, 0.5)
      // mesh.scale.set(5000, 5000, 5000)

      // mesh.castShadow = true
      // mesh.receiveShadow = true
      resolve(mesh)
    })
  })
}

export function loadObj(model, materials) {
  const loader = new OBJLoader()
  // objLoader.setPath('/model/')
  materials && loader.setMaterials(materials)
  return new Promise((resolve, reject) => {
    loader.load(model,
      obj => resolve(obj),
      progress => Math.trunc(progress.loaded / progress.total * 100) % 100,
      reject,
    )
  })
}

export function intersect(event, {camera, scene, renderer}, objects = scene.children) {
  const raycaster = new THREE.Raycaster()
  const mouse = new THREE.Vector2()
  const canvas = renderer.domElement
  mouse.x = (event.offsetX / canvas.offsetWidth) * 2 - 1
  mouse.y = -(event.offsetY / canvas.offsetHeight) * 2 + 1
  raycaster.setFromCamera(mouse, camera)

  let intersects = objects instanceof Array ? raycaster.intersectObjects(objects) : raycaster.intersectObject(objects)
  intersects = intersects.slice(0, 1)

  const lastIntersects = scene._intersects || []
  scene._intersects = intersects

  lastIntersects
    .forEach(intersect => {
      if (objects instanceof THREE.InstancedMesh) {
        objects.setColorAt(intersect.instanceId, objects._color)
        objects.instanceColor.needsUpdate = true
      }
    })
  intersects
    .forEach(intersect => {
      if (objects instanceof THREE.InstancedMesh) {
        objects.setColorAt(intersect.instanceId, new THREE.Color(1, 0, 0))
        objects.instanceColor.needsUpdate = true
      }
    })

  return intersects[0]
}

export function addBar({scene}, position = [0, 0, 0]) {
  const geometry = new THREE.BoxGeometry(10, 20, 5)
  const material = new THREE.MeshPhongMaterial({color: 0xffffff})
  material.transparent = true
  material.opacity = 0.9

  const bar = new THREE.Mesh(geometry, material)
  bar.position.set(...position)
  scene.add(bar)
  return bar
}

export function outlineObj(selectedObjects, {scene, camera, renderer}) {
  const composer = new EffectComposer(renderer) // 特效组件

  const renderPass = new RenderPass(scene, camera)
  composer.addPass(renderPass) // 特效渲染

  const effect1 = new ShaderPass(DotScreenShader)
  effect1.uniforms['scale'].value = 4
  composer.addPass(effect1)

  const effect2 = new ShaderPass(RGBShiftShader)
  effect2.uniforms['amount'].value = 0.0015
  composer.addPass(effect2)

  // const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85)
  // bloomPass.renderToScreen = true
  // bloomPass.threshold = 0
  // bloomPass.strength = 0.2
  // bloomPass.radius = 0
  //
  // renderer.gammaInput = true
  // renderer.gammaOutput = true

  // const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera)
  // composer.addPass(outlinePass) // 加入高光特效

  // outlinePass.pulsePeriod = 2 // 数值越大，律动越慢
  // outlinePass.visibleEdgeColor.set(0x0088ff) // 高光颜色
  // outlinePass.hiddenEdgeColor.set(0x000000)// 阴影颜色
  // outlinePass.usePatternTexture = false // 使用纹理覆盖？
  // outlinePass.edgeStrength = 5 // 高光边缘强度
  // outlinePass.edgeGlow = 1 // 边缘微光强度
  // outlinePass.edgeThickness = 1 // 高光厚度

  // outlinePass.selectedObjects = selectedObjects // 需要高光的obj

  // composer.addPass(bloomPass)

  return composer
}

export function addSquare(parent, size = 20) {
  const vertices = [
    [0, 0, 0],
    [size, size, 0],
    [0, size, 0],
    [0, 0, 0],
    [size, 0, 0],
    [size, size, 0],
  ]
  return addFaces(parent, vertices)
}

export function addFaces(parent, vertices, {
  material = new THREE.MeshPhongMaterial({color: 0x33ddff, specular: 0x111111, shininess: 30, side: THREE.DoubleSide}),
  normals = vertices.map(_ => ([0, 1, 0]))
} = {}) {
  const geometry = new THREE.BufferGeometry()
  geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices.flat()), 3))
  geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(normals.flat()), 3))
  const mesh = new THREE.Mesh(geometry, material)
  parent.add(mesh)

  return mesh
}

export function addCube({scene}, size = 20) {
  const geometry = new THREE.BoxGeometry(size, size, size)
  const material = new THREE.MeshPhongMaterial({color: 0xffffff})
  const mesh = new THREE.Mesh(geometry, material)
  scene.add(mesh)
  return mesh
}

export function addSpheres({scene}) {
  const spheres = new THREE.Object3D()
  scene.add(spheres)

  const geometry = new THREE.SphereGeometry(1, 4, 4)
  const material = new THREE.MeshPhongMaterial({color: 0xffffff, flatShading: true})

  for (let i = 0; i < 100; i++) {
    const mesh = new THREE.Mesh(geometry, material)
    mesh.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5).normalize()
    mesh.position.multiplyScalar(Math.random() * 400)
    mesh.rotation.set(Math.random() * 2, Math.random() * 2, Math.random() * 2)
    mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 50
    spheres.add(mesh)
  }
  return spheres
}

export function addShader({renderer, scene, camera}) {
  const composer = new EffectComposer(renderer)
  composer.addPass(new RenderPass(scene, camera))

  const effect1 = new ShaderPass(DotScreenShader)
  effect1.uniforms['scale'].value = 1
  composer.addPass(effect1)

  const effect2 = new ShaderPass(RGBShiftShader)
  effect2.uniforms['amount'].value = 0.0115
  composer.addPass(effect2)

  // Bloom通道创建
  const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), .5, 0.2, 0.85)
  bloomPass.renderToScreen = true
  bloomPass.threshold = 0
  bloomPass.strength = .6
  bloomPass.radius = 2

  composer.setSize(window.innerWidth, window.innerHeight)
  // 眩光通道bloomPass插入到composer
  composer.addPass(bloomPass)

  return composer
}
